package com.wanfan.wanfanBrush.design.countingPolicy;

import cn.dev33.satoken.stp.StpUtil;
import com.wanfan.wanfanBrush.common.ErrorCode;
import com.wanfan.wanfanBrush.exception.BusinessException;
import com.wanfan.wanfanBrush.model.entity.User;
import com.wanfan.wanfanBrush.service.UserService;
import com.wanfan.wanfanBrush.utils.EmailUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static com.wanfan.wanfanBrush.constant.EmailConstant.*;

/**
 * 基于本地计数器的反爬虫检测策略
 */
@Service
@Slf4j
public class LocalCounterAntiCrawlerStrategy implements AntiCrawlerStrategy {

    @Resource
    private UserService userService;

    // 本地计数器，记录用户访问次数
    private static final ConcurrentMap<Long, AtomicInteger> accessCountMap = new ConcurrentHashMap<>();
    // 记录用户首次访问时间
    private static final ConcurrentMap<Long, AtomicLong> firstAccessTimeMap = new ConcurrentHashMap<>();
    // 调用多少次时告警
    private static final int WARN_COUNT = 10;
    // 超过多少次封号
    private static final int BAN_COUNT = 20;
    // 统计时间窗口（1 分钟）
    private static final long TIME_WINDOW = 60 * 1000;

    @Override
    public void detect(long loginUserId) throws BusinessException {
        long currentTime = System.currentTimeMillis();
        // 初始化首次访问时间
        firstAccessTimeMap.computeIfAbsent(loginUserId, k -> new AtomicLong(currentTime));
        long firstAccessTime = firstAccessTimeMap.get(loginUserId).get();

        // 检查是否超出时间窗口，超出则重置计数器
        if (currentTime - firstAccessTime > TIME_WINDOW) {
            accessCountMap.put(loginUserId, new AtomicInteger(0));
            firstAccessTimeMap.get(loginUserId).set(currentTime);
        }

        // 增加访问次数
        int count = accessCountMap.computeIfAbsent(loginUserId, k -> new AtomicInteger(0)).incrementAndGet();

        // 是否封号
        if (count > BAN_COUNT) {
            // 踢下线
            StpUtil.kickout(loginUserId);
            // 封号
            User updateUser = new User();
            updateUser.setId(loginUserId);
            updateUser.setUserRole("ban");
            userService.updateById(updateUser);
            try {
                String subject = "用户访问异常封号通知";
                String content = String.format("用户 ID %d 访问过于频繁，已被封号。", loginUserId);
                EmailUtils.sendEmail(SENDER_EMAIL, SENDER_PASSWORD, ADMIN_EMAIL, subject, content);
            } catch (MessagingException e) {
                log.error("发送封号通知邮件失败", e);
            }
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "访问太频繁，已被封号");
        }
        // 是否警告
        if (count == WARN_COUNT) {
            try {
                String subject = "用户访问异常警告通知";
                String content = String.format("用户 ID %d 访问过于频繁，请关注。", loginUserId);
                EmailUtils.sendEmail(SENDER_EMAIL, SENDER_PASSWORD, ADMIN_EMAIL, subject, content);
            } catch (MessagingException e) {
                log.error("发送警告通知邮件失败", e);
            }
            throw new BusinessException(110, "警告访问太频繁");
        }
    }
}